use std::io::Write;
use std::sync::Arc;

use rustyline_async::{Readline, ReadlineEvent, SharedWriter};

use crate::ServerState;

mod avatar;
mod gacha;
mod item;
mod player;

type ArgSlice<'a> = &'a [&'a str];

pub struct CommandManager {
    state: Arc<ServerState>,
}

macro_rules! commands {
    ($($category:ident::$action:ident $usage:tt $desc:tt;)*) => {
        async fn exec(state: &ServerState, cmd: &str) -> String {
            let input = cmd.split(" ").collect::<Vec<&str>>();

            if input.len() == 1 && *input.get(0).unwrap() == "help" {
                return Self::help_message();
            }

            let (Some(category), Some(action)) = (input.get(0), input.get(1)) else {
                return String::new();
            };

            let args = &input[2..];
            match match (*category, *action) {
                $(
                    (stringify!($category), stringify!($action)) => {
                        $category::$action(args, state).await
                    }
                )*,
                _ => {
                    ::tracing::info!("unknown command");
                    return Self::help_message();
                }
            } {
                Ok(s) => s,
                Err(err) => format!("failed to execute command: {err}"),
            }
        }

        fn help_message() -> String {
            concat!("available commands:\n",
                $(stringify!($category), " ", stringify!($action), " ", $usage, " - ", $desc, "\n",)*
                "help - shows this message"
            ).to_string()
        }
    };
}

impl CommandManager {
    pub fn new(state: Arc<ServerState>) -> Self {
        Self { state }
    }

    pub async fn run(&self, mut rl: Readline, mut out: SharedWriter) {
        loop {
            match rl.readline().await {
                Ok(ReadlineEvent::Line(line)) => {
                    let str = Self::exec(&self.state, &line).await;
                    writeln!(&mut out, "{str}").unwrap();

                    rl.add_history_entry(line);
                }
                Ok(ReadlineEvent::Eof) | Ok(ReadlineEvent::Interrupted) => {
                    rl.flush().unwrap();
                    drop(rl);

                    // TODO: maybe disconnect and save all players

                    std::process::exit(0);
                }
                _ => continue,
            }
        }
    }

    commands! {
        player::avatar "[player_uid] [avatar_id]" "changes player avatar for main city";
        player::nickname "[player_uid] [nickname]" "changes player nickname";
        player::procedure "[player_uid] [procedure_id]" "changes current beginner procedure id, parameter -1 can be used for skipping it";
        player::kick "[player_uid] [reason]" "kick the specified player (reason is optional)";
        avatar::add "[player_uid] [avatar_id]" "gives avatar with specified id to player";
        avatar::add_all "[player_uid]" "gives all avatars to player";
        item::add_weapon "[player_uid] [weapon_id]" "gives weapon with specified id to player";
        gacha::up "[player_uid]" "start a gacha UP setting guide (available for Bangboo pool)";
    }
}
